// Syntax parser module

// import Lexer module
import Lex = require("./LexicalParser");

// import the command structure
import Cmd = require("../Execution/Commands");

// import instructions module
import Instr = require("../Execution/Instructions");

// import dictionary module
import Dic = require("../Execution/Dictionary");

// localization reference
import Loc = require("../../UI/Localization");


// the exception raised when a syntax error has occured
export class SyntaxErrorException {
    // private attributes
    private mMsgId: string;
    private mParams: Array<any>;

    // reference localization
    private mLocalization: Loc.Localization;

    // constructor
    public constructor(msgId: string, params: Array<any>) {
        // init vars
        this.mMsgId = msgId;
        this.mParams = new Array<any>();
        for (var i = 0; i < params.length; i++) {
            this.mParams.push(params[i]);
        }

        // set references
        this.mLocalization = Loc.Localization.Instance;
    }

    // returns the formatted user error message that has occurred
    public GetErrorMessage(): string {
        var msg = this.mLocalization.Get(this.mMsgId);
        return this.FormatMessage(msg, this.mParams);
    }

    // formats the message
    private FormatMessage(msg: string, params: Array<any>): string {
        var finaMsg: string = msg;

        for (var i: number = 0; i < params.length; i++) {
            finaMsg = finaMsg.replace("{" + i.toString() + "}", params[i].toString());
        }

        return finaMsg;       
    }
}


// Syntax rule class
class Rule {
    public Symbol: Lex.eSymbolType;
    public Rule: Array<Lex.eSymbolType>;
    public CatchSymbols: Array<Lex.eSymbolType>;

    // constructor
    public constructor() {
        this.Symbol = null;
        this.Rule = new Array<Lex.eSymbolType>();
        this.CatchSymbols = new Array<Lex.eSymbolType>();
    }
}



// The syntax parser class itself
export class SyntaxParser {

    // parsing rules
    private mRules: Array<Rule>;

    // the input stream
    private mStream: Lex.LexicalStream;

    // command creation methods
    private mCmdStack: Array<Cmd.Command>;
    private mCommandName: string;
    private mFinalCommand: Cmd.UserCommand;

    // temporary varibles for parsing condition
    private mExistOperator: Instr.eConditionOperator;

    // stack of syntax symbols
    private mSyntaxStack: Array<Lex.eSymbolType>;

    // references to other modules
    private mDictionary: Dic.Dictionary;

    // constructor
    public constructor(stream: Lex.LexicalStream) {
        // create the syntax rule structure
        this.mRules = new Array<Rule>();
        this.CreateParsingRules();

        // set references
        this.mDictionary = Dic.Dictionary.Instance;       

        // init vars
        this.mStream = stream;
        this.mCmdStack = new Array<Cmd.Command>();
        this.mCommandName = "";
        this.mFinalCommand = null;
        this.mSyntaxStack = new Array<Lex.eSymbolType>();
    }

    // The main API methods
    ////////////////////////////////////////////////////////////////////////////////////////////////////////

    // returns the final command - property getter
    public get FinalCommand() {
        return this.mFinalCommand;
    }

    // The main parsing method
    public Parse(): void {
        // init the syntax stack - add the root symbol
        this.mSyntaxStack.push(Lex.eSymbolType.usercommand);

        // do syntax parsing
        while (this.mSyntaxStack.length > 0) {
            // check end of lexical stream
            if (this.mStream.EOF)
                this.ThrowException("msgUnexpectedEnd");

            // get the next symbols
            var lexSymbol = this.mStream.Get();
            var syntaxSymbol = this.mSyntaxStack.pop();

            // compare symbols
            if (lexSymbol.SymbolType == syntaxSymbol) {
                
                // process the symbol
                this.ProcessSymbol(lexSymbol);


            } else {
                this.mStream.Unget();

                // try to find rule
                var rule = this.FindParsingRule(syntaxSymbol);
                if (rule != null) {

                    // if rule found, push it onto the stack
                    for (var i = rule.Rule.length - 1; i >= 0; i--) {
                        this.mSyntaxStack.push(rule.Rule[i]);
                    }
                }
                else {
                    this.ThrowExceptionBySymbolType();
                }
            }
        } // while
    }

    // Processing methods
    ////////////////////////////////////////////////////////////////////////////////////////////////////////

    // the main processing method building the command stream
    private ProcessSymbol(symbol: Lex.Symbol): void {
        // pop the parent CommandBlock, if any
        var poppedCommand: Cmd.Command = null;
        var parentBlock: Cmd.CommandBlock = null;
        if (this.mCmdStack.length > 0) {
            poppedCommand = this.mCmdStack.pop();
            if (poppedCommand instanceof Cmd.CommandBlock) {
                parentBlock = <Cmd.CommandBlock>poppedCommand;
            }
        }

        // temporary variables
        var cmd: Cmd.Command = null;
        var condition: Instr.Condition = null;

        // switch according to the symbol type
        switch (symbol.SymbolType) {
            // elementary system commands
            case Lex.eSymbolType.STEP:
                cmd = new Cmd.InternalCommand(Instr.eInternalCommand.Move);
                parentBlock.AddCommand(cmd);
                this.mCmdStack.push(parentBlock);
                break;
            case Lex.eSymbolType.TURNLEFT:
                cmd = new Cmd.InternalCommand(Instr.eInternalCommand.TurnLeft);
                parentBlock.AddCommand(cmd);
                this.mCmdStack.push(parentBlock);
                break;
            case Lex.eSymbolType.PUT:
                cmd = new Cmd.InternalCommand(Instr.eInternalCommand.Put);
                parentBlock.AddCommand(cmd);
                this.mCmdStack.push(parentBlock);
                break;
            case Lex.eSymbolType.PICK:
                cmd = new Cmd.InternalCommand(Instr.eInternalCommand.Pick);
                parentBlock.AddCommand(cmd);
                this.mCmdStack.push(parentBlock);
                break;

            // the End-of-block command
            case Lex.eSymbolType.END:
                // if termination
                if (this.mCmdStack.length == 0) {
                    this.mFinalCommand = <Cmd.UserCommand>parentBlock;
                    break;
                }
                else {
                    if (this.mStream.EOF == false) {
                        // check, if there is IF command, the remove it, only in case if not followed by ELSE block
                        if (this.mStream.Peek().SymbolType != Lex.eSymbolType.ELSE) {
                            if (this.mCmdStack.length > 0) {
                                cmd = this.mCmdStack.pop();
                                if (cmd instanceof Cmd.IfCommand == false) {
                                    this.mCmdStack.push(cmd);
                                }
                            }
                        }
                    }
                }       
                break;

            // Building Condition
            case Lex.eSymbolType.IS:
                this.mExistOperator = Instr.eConditionOperator.Is;
                this.mCmdStack.push(poppedCommand);
                break;
            case Lex.eSymbolType.ISNOT:
                this.mExistOperator = Instr.eConditionOperator.IsNot;
                this.mCmdStack.push(poppedCommand);
                break;
            case Lex.eSymbolType.NORTH:
                condition = new Instr.Condition(this.mExistOperator, Instr.eConditionType.North);
                break;
            case Lex.eSymbolType.SOUTH:
                condition = new Instr.Condition(this.mExistOperator, Instr.eConditionType.South);
                break;
            case Lex.eSymbolType.WEST:
                condition = new Instr.Condition(this.mExistOperator, Instr.eConditionType.West);
                break;
            case Lex.eSymbolType.EAST:
                condition = new Instr.Condition(this.mExistOperator, Instr.eConditionType.East);
                break;
            case Lex.eSymbolType.MARK:
                condition = new Instr.Condition(this.mExistOperator, Instr.eConditionType.Mark);
                break;
            case Lex.eSymbolType.WALL:
                condition = new Instr.Condition(this.mExistOperator, Instr.eConditionType.Wall);
                break;

            // repetition count record
            case Lex.eSymbolType.RepeatCount:
                if (poppedCommand instanceof Cmd.RepeatCommand) {
                    (<Cmd.RepeatCommand>poppedCommand).SetRepetitionCount(symbol.NumberValue);
                    this.mCmdStack.push(poppedCommand);
                }
                else this.ThrowException("msgWrongPlaceForRepetion");
                break;

            // user command
            case Lex.eSymbolType.CommandName:
                // create the user command
                var userCmd = new Cmd.UserCommand(symbol.StringValue);

                // if it is the root command 
                if (poppedCommand == null) {
                    this.mCommandName = userCmd.GetName();
                    this.mCmdStack.push(userCmd);
                }
                else {
                    if (!(symbol.StringValue == this.mCommandName || this.mDictionary.ExistsUserCommand(symbol.StringValue))) {
                        this.ThrowException("msgUnknownCommand", symbol.StringValue.toLocaleUpperCase());
                    }

                    // add command to the parent block
                    parentBlock.AddCommand(userCmd);
                    this.mCmdStack.push(parentBlock);
                }                                  
                break;

            // iteration commands - cycles
            case Lex.eSymbolType.WHILE:
                var whileCmd = new Cmd.WhileCommand();
                parentBlock.AddCommand(whileCmd);                
                this.mCmdStack.push(parentBlock);
                this.mCmdStack.push(whileCmd);
                break;
            case Lex.eSymbolType.REPEAT:
                var repeatCmd = new Cmd.RepeatCommand();
                parentBlock.AddCommand(repeatCmd);
                this.mCmdStack.push(parentBlock);
                this.mCmdStack.push(repeatCmd);
                break;

            // branching command IF
            case Lex.eSymbolType.IF:
                var ifCmd = new Cmd.IfCommand();
                parentBlock.AddCommand(ifCmd);
                this.mCmdStack.push(parentBlock);
                this.mCmdStack.push(ifCmd);
                break;
            case Lex.eSymbolType.ELSE:
                if (poppedCommand instanceof Cmd.IfCommand) {
                    var ifCommand = <Cmd.IfCommand>poppedCommand;
                    var elseBlock = new Cmd.CommandBlock();
                    ifCommand.AttachElseBlock(elseBlock);
                    this.mCmdStack.push(ifCommand);
                    this.mCmdStack.push(elseBlock);
                }
                else this.ThrowException("msgWrongPlaceForELSE");
                break;

            // error handling
            default:
                throw "Unknown symbol type";
        }

        // check, if condition has been parsed
        if (condition != null) {
            // check IF command
            if (poppedCommand instanceof Cmd.IfCommand) {
                var ifCommand = <Cmd.IfCommand>poppedCommand;
                ifCommand.SetCondition(condition);
                var thenBlock = new Cmd.CommandBlock();
                ifCommand.AttachThenBlock(thenBlock);

                this.mCmdStack.push(ifCommand);
                this.mCmdStack.push(thenBlock);
            }
            // check WHILE command
            else if (poppedCommand instanceof Cmd.WhileCommand) {
                (<Cmd.WhileCommand>poppedCommand).SetCondition(condition);
                this.mCmdStack.push(poppedCommand);
            }
            else this.ThrowException("msgWrongPlaceForCondition");
        }
    }

     // Parsing methods
    ////////////////////////////////////////////////////////////////////////////////////////////////////////

    // looks up the Rules array to find a rule
    private FindParsingRule(symbol: Lex.eSymbolType): Rule {
        // container for found rules
        var rules: Array<Rule> = new Array<Rule>();
        var rule: Rule = null;

        // find rules by leading symbols
        for (var i = 0; i < this.mRules.length; i++) {
            rule = this.mRules[i];
            if (rule.Symbol == symbol) {
                rules.push(rule);
            }
        }

        // filter out rules by catch symbols
        var rules2: Array<Rule> = new Array<Rule>();
        for (var i = 0; i < rules.length; i++) {
            rule = rules[i];
            
            // if no catch symbol, push it
            if (rule.CatchSymbols.length == 0) {
                rules2.push(rule);
                continue;
            }

            // push rules with the epsilon symbol - means whatever
            if (rule.CatchSymbols[0] == Lex.eSymbolType.epsilon) {
                rules2.push(rule);
                continue;
            }

            // check the catch symbols
            var catchSymbol: Lex.Symbol = this.mStream.Peek();
            for (var j = 0; j < rule.CatchSymbols.length; j++) {
                if (rule.CatchSymbols[j] == catchSymbol.SymbolType) {
                    rules2.push(rule);
                    break;
                }
            }
        }

        // if no rule, return NULL value
        if (rules2.length == 0)
            return null;
        // if one rule, return it
        if (rules2.length == 1) {
            return rules2[0];
        }

        // more rules, filter out epsilon and empty rules
        var rules3: Array<Rule> = new Array<Rule>();
        for (var i = 0; i < rules2.length; i++) {
            rule = rules2[i];
            if (rule.CatchSymbols.length == 0)
                continue;
            if (rule.CatchSymbols[0] == Lex.eSymbolType.epsilon)
                continue;
            rules3.push(rule);
        }

        // if no rule, return NULL value
        if (rules3.length == 0)
            return null;
        // if one rule, return it
        if (rules3.length == 1) {
            return rules2[0];
        }

        // more rules, throw exception
        throw "Conflicting rules.";
    }



     // Rule Creation Structure
    ////////////////////////////////////////////////////////////////////////////////////////////////////////

    // creates the list of parsing rules
    private CreateParsingRules(): void {
        // Condition rules
        this.AddParsingRule(Lex.eSymbolType.operator, [Lex.eSymbolType.IS], [Lex.eSymbolType.IS]);
        this.AddParsingRule(Lex.eSymbolType.operator, [Lex.eSymbolType.ISNOT], [Lex.eSymbolType.ISNOT]);
        this.AddParsingRule(Lex.eSymbolType.query, [Lex.eSymbolType.NORTH], [Lex.eSymbolType.NORTH]);
        this.AddParsingRule(Lex.eSymbolType.query, [Lex.eSymbolType.SOUTH], [Lex.eSymbolType.SOUTH]);
        this.AddParsingRule(Lex.eSymbolType.query, [Lex.eSymbolType.WEST], [Lex.eSymbolType.WEST]);
        this.AddParsingRule(Lex.eSymbolType.query, [Lex.eSymbolType.EAST], [Lex.eSymbolType.EAST]);
        this.AddParsingRule(Lex.eSymbolType.query, [Lex.eSymbolType.WALL], [Lex.eSymbolType.WALL]);
        this.AddParsingRule(Lex.eSymbolType.query, [Lex.eSymbolType.MARK], [Lex.eSymbolType.MARK]);
        this.AddParsingRule(Lex.eSymbolType.condition, [Lex.eSymbolType.operator, Lex.eSymbolType.query], [Lex.eSymbolType.IS, Lex.eSymbolType.ISNOT]);

        // Command rules - elementary commands
        this.AddParsingRule(Lex.eSymbolType.command, [Lex.eSymbolType.STEP], [Lex.eSymbolType.STEP]);
        this.AddParsingRule(Lex.eSymbolType.command, [Lex.eSymbolType.TURNLEFT], [Lex.eSymbolType.TURNLEFT]);
        this.AddParsingRule(Lex.eSymbolType.command, [Lex.eSymbolType.PUT], [Lex.eSymbolType.PUT]);
        this.AddParsingRule(Lex.eSymbolType.command, [Lex.eSymbolType.PICK], [Lex.eSymbolType.PICK]);
        this.AddParsingRule(Lex.eSymbolType.command, [Lex.eSymbolType.CommandName], [Lex.eSymbolType.CommandName]);

        // Command-block rules
        this.AddParsingRule(Lex.eSymbolType.commandblock, [Lex.eSymbolType.command, Lex.eSymbolType.commandblock2], []);
        this.AddParsingRule(Lex.eSymbolType.commandblock2, [Lex.eSymbolType.command, Lex.eSymbolType.commandblock2],
            [Lex.eSymbolType.STEP, Lex.eSymbolType.TURNLEFT, Lex.eSymbolType.PICK, Lex.eSymbolType.PUT, Lex.eSymbolType.CommandName,
                Lex.eSymbolType.IF, Lex.eSymbolType.WHILE, Lex.eSymbolType.REPEAT]); 
        this.AddParsingRule(Lex.eSymbolType.commandblock2, [Lex.eSymbolType.END], [Lex.eSymbolType.END]);

        // Command rules - composite command
        this.AddParsingRule(Lex.eSymbolType.elseblock, [Lex.eSymbolType.ELSE, Lex.eSymbolType.commandblock], [Lex.eSymbolType.ELSE]);
        this.AddParsingRule(Lex.eSymbolType.elseblock, [], [Lex.eSymbolType.epsilon]); 
        this.AddParsingRule(Lex.eSymbolType.command, [Lex.eSymbolType.IF, Lex.eSymbolType.condition, Lex.eSymbolType.commandblock, Lex.eSymbolType.elseblock], [Lex.eSymbolType.IF]);

        this.AddParsingRule(Lex.eSymbolType.command, [Lex.eSymbolType.REPEAT, Lex.eSymbolType.RepeatCount, Lex.eSymbolType.commandblock], [Lex.eSymbolType.REPEAT]);
        this.AddParsingRule(Lex.eSymbolType.command, [Lex.eSymbolType.WHILE, Lex.eSymbolType.condition, Lex.eSymbolType.commandblock], [Lex.eSymbolType.WHILE]);

        // Root rule
        this.AddParsingRule(Lex.eSymbolType.usercommand, [Lex.eSymbolType.CommandName, Lex.eSymbolType.commandblock], []);
    }

    // adds parsing rule
    private AddParsingRule(symbol: Lex.eSymbolType, rule: Array<Lex.eSymbolType>, catchSymbols: Array<Lex.eSymbolType>): void {
        var r = new Rule();
        r.Symbol = symbol;
        r.Rule = rule;
        r.CatchSymbols = catchSymbols;
        this.mRules.push(r);
    }

    // throws syntax error exception by the type of symbol that no rule has been found for
    private ThrowExceptionBySymbolType() {
        if (this.mStream.EOF) {
            this.ThrowException("msgUnexpectedEnd");
        }

        var symbol = this.mStream.Get();

        switch (symbol.SymbolType) {
            case Lex.eSymbolType.CommandName:
                this.ThrowException("msgUnknownCommand", symbol.StringValue.toLocaleUpperCase());
                break;
            case Lex.eSymbolType.RepeatCount:
                this.ThrowException("msgWrongPlaceForRepetion");
                break;
            case Lex.eSymbolType.IS:
            case Lex.eSymbolType.ISNOT:
                this.ThrowException("msgWrongPlaceForCondition");
                break;
            case Lex.eSymbolType.WALL:
            case Lex.eSymbolType.MARK:
            case Lex.eSymbolType.NORTH:
            case Lex.eSymbolType.SOUTH:
            case Lex.eSymbolType.WEST:
            case Lex.eSymbolType.EAST:
                this.ThrowException("msgWrongConditionType");
                break;
            case Lex.eSymbolType.END:
                this.ThrowException("msgNoEmptyBlock");
                break;
            case Lex.eSymbolType.ELSE:
                this.ThrowException("msgWrongPlaceForELSE");
                break;
            case Lex.eSymbolType.STEP:
            case Lex.eSymbolType.TURNLEFT:
            case Lex.eSymbolType.PICK:
            case Lex.eSymbolType.PUT:
                this.ThrowException("msgWrongPlaceCommand");
                break;
            case Lex.eSymbolType.WHILE:
            case Lex.eSymbolType.IF:
            case Lex.eSymbolType.REPEAT:
                this.ThrowException("msgWrongPlaceCommand2");
                break;
            default:
                this.ThrowException("msgSyntaxError");
                break;
        }
    }

    // throws the syntax exception
    private ThrowException(msgId: string, ...params) {
        throw new SyntaxErrorException(msgId, params);
    }
}
